#![allow(unused)]

use std::fmt;
use libc::{
    syscall,
    c_int,
    c_ulong,
    pid_t
};

#[cfg(target_endian = "big")]
macro_rules! flag {
    ($nth:expr) => {(1 << 63) >> $nth}
}

#[cfg(target_endian = "little")]
macro_rules! flag {
    ($nth:expr) => {1 << $nth}
}

pub const PERF_FLAG_FD_CLOEXEC: c_ulong = 1 << 3;

pub const PERF_TYPE_HARDWARE: u32 = 0;
pub const PERF_TYPE_SOFTWARE: u32 = 1;
pub const PERF_TYPE_TRACEPOINT: u32 = 2;

pub const PERF_ATTR_FLAG_DISABLED: u64                  = flag!( 0 );
pub const PERF_ATTR_FLAG_INHERIT: u64                   = flag!( 1 );
pub const PERF_ATTR_FLAG_PINNED: u64                    = flag!( 2 );
pub const PERF_ATTR_FLAG_EXCLUSIVE: u64                 = flag!( 3 );
pub const PERF_ATTR_FLAG_EXCLUDE_USER: u64              = flag!( 4 );
pub const PERF_ATTR_FLAG_EXCLUDE_KERNEL: u64            = flag!( 5 );
pub const PERF_ATTR_FLAG_EXCLUDE_HV: u64                = flag!( 6 );
pub const PERF_ATTR_FLAG_EXCLUDE_IDLE: u64              = flag!( 7 );
pub const PERF_ATTR_FLAG_MMAP: u64                      = flag!( 8 );
pub const PERF_ATTR_FLAG_COMM: u64                      = flag!( 9 );
pub const PERF_ATTR_FLAG_FREQ: u64                      = flag!( 10 );
pub const PERF_ATTR_FLAG_INHERIT_STAT: u64              = flag!( 11 );
pub const PERF_ATTR_FLAG_ENABLE_ON_EXEC: u64            = flag!( 12 );
pub const PERF_ATTR_FLAG_TASK: u64                      = flag!( 13 );
pub const PERF_ATTR_FLAG_WATERMARK: u64                 = flag!( 14 );
pub const PERF_ATTR_FLAG_MMAP_DATA: u64                 = flag!( 17 );
pub const PERF_ATTR_FLAG_SAMPLE_ID_ALL: u64             = flag!( 18 );
pub const PERF_ATTR_FLAG_EXCLUDE_HOST: u64              = flag!( 19 );
pub const PERF_ATTR_FLAG_EXCLUDE_GUEST: u64             = flag!( 20 );
pub const PERF_ATTR_FLAG_EXCLUDE_CALLCHAIN_KERNEL: u64  = flag!( 21 );
pub const PERF_ATTR_FLAG_EXCLUDE_CALLCHAIN_USER: u64    = flag!( 22 );
pub const PERF_ATTR_FLAG_MMAP2: u64                     = flag!( 23 );
pub const PERF_ATTR_FLAG_COMM_EXEC: u64                 = flag!( 24 );
pub const PERF_ATTR_FLAG_USE_CLOCKID: u64               = flag!( 25 );
pub const PERF_ATTR_FLAG_CONTEX_SWITCH: u64             = flag!( 26 );

pub const PERF_COUNT_HW_CPU_CYCLES: u64 = 0;
pub const PERF_COUNT_HW_REF_CPU_CYCLES: u64 = 9;

pub const PERF_COUNT_SW_CPU_CLOCK: u64 = 0;
pub const PERF_COUNT_SW_TASK_CLOCK: u64 = 1;
pub const PERF_COUNT_SW_PAGE_FAULTS: u64 = 2;
pub const PERF_COUNT_SW_DUMMY: u64 = 9;

pub const PERF_RECORD_LOST: u32 = 2;
pub const PERF_RECORD_COMM: u32 = 3;
pub const PERF_RECORD_EXIT: u32 = 4;
pub const PERF_RECORD_THROTTLE: u32 = 5;
pub const PERF_RECORD_UNTHROTTLE: u32 = 6;
pub const PERF_RECORD_FORK: u32 = 7;
pub const PERF_RECORD_SAMPLE: u32 = 9;
pub const PERF_RECORD_MMAP2: u32 = 10;
pub const PERF_RECORD_SWITCH: u32 = 14;

pub const PERF_RECORD_MISC_SWITCH_OUT: u16 = 1 << 13;
pub const PERF_RECORD_MISC_SWITCH_OUT_PREEMPT: u16 = 1 << 14;

pub const PERF_SAMPLE_IP: u64              = 1 << 0;
pub const PERF_SAMPLE_TID: u64             = 1 << 1;
pub const PERF_SAMPLE_TIME: u64            = 1 << 2;
pub const PERF_SAMPLE_ADDR: u64            = 1 << 3;
pub const PERF_SAMPLE_READ: u64            = 1 << 4;
pub const PERF_SAMPLE_CALLCHAIN: u64       = 1 << 5;
pub const PERF_SAMPLE_ID: u64              = 1 << 6;
pub const PERF_SAMPLE_CPU: u64             = 1 << 7;
pub const PERF_SAMPLE_PERIOD: u64          = 1 << 8;
pub const PERF_SAMPLE_STREAM_ID: u64       = 1 << 9;
pub const PERF_SAMPLE_RAW: u64             = 1 << 10;
pub const PERF_SAMPLE_BRANCH_STACK: u64    = 1 << 11;
pub const PERF_SAMPLE_REGS_USER: u64       = 1 << 12;
pub const PERF_SAMPLE_STACK_USER: u64      = 1 << 13;
pub const PERF_SAMPLE_WEIGHT: u64          = 1 << 14;
pub const PERF_SAMPLE_DATA_SRC: u64        = 1 << 15;
pub const PERF_SAMPLE_IDENTIFIER: u64      = 1 << 16;
pub const PERF_SAMPLE_TRANSACTION: u64     = 1 << 17;
pub const PERF_SAMPLE_REGS_INTR: u64       = 1 << 18;

pub const PERF_REG_X86_AX: u64 = 0;
pub const PERF_REG_X86_BX: u64 = 1;
pub const PERF_REG_X86_CX: u64 = 2;
pub const PERF_REG_X86_DX: u64 = 3;
pub const PERF_REG_X86_SI: u64 = 4;
pub const PERF_REG_X86_DI: u64 = 5;
pub const PERF_REG_X86_BP: u64 = 6;
pub const PERF_REG_X86_SP: u64 = 7;
pub const PERF_REG_X86_IP: u64 = 8;
pub const PERF_REG_X86_FLAGS: u64 = 9;
pub const PERF_REG_X86_CS: u64 = 10;
pub const PERF_REG_X86_SS: u64 = 11;
pub const PERF_REG_X86_DS: u64 = 12;
pub const PERF_REG_X86_ES: u64 = 13;
pub const PERF_REG_X86_FS: u64 = 14;
pub const PERF_REG_X86_GS: u64 = 15;
pub const PERF_REG_X86_R8: u64 = 16;
pub const PERF_REG_X86_R9: u64 = 17;
pub const PERF_REG_X86_R10: u64 = 18;
pub const PERF_REG_X86_R11: u64 = 19;
pub const PERF_REG_X86_R12: u64 = 20;
pub const PERF_REG_X86_R13: u64 = 21;
pub const PERF_REG_X86_R14: u64 = 22;
pub const PERF_REG_X86_R15: u64 = 23;

pub const PERF_REG_X86_32_MAX: u64 = PERF_REG_X86_GS + 1;
pub const PERF_REG_X86_64_MAX: u64 = PERF_REG_X86_R15 + 1;

pub const PERF_REG_ARM_R0: u64 = 0;
pub const PERF_REG_ARM_R1: u64 = 1;
pub const PERF_REG_ARM_R2: u64 = 2;
pub const PERF_REG_ARM_R3: u64 = 3;
pub const PERF_REG_ARM_R4: u64 = 4;
pub const PERF_REG_ARM_R5: u64 = 5;
pub const PERF_REG_ARM_R6: u64 = 6;
pub const PERF_REG_ARM_R7: u64 = 7;
pub const PERF_REG_ARM_R8: u64 = 8;
pub const PERF_REG_ARM_R9: u64 = 9;
pub const PERF_REG_ARM_R10: u64 = 10;
pub const PERF_REG_ARM_FP: u64 = 11;
pub const PERF_REG_ARM_IP: u64 = 12;
pub const PERF_REG_ARM_SP: u64 = 13;
pub const PERF_REG_ARM_LR: u64 = 14;
pub const PERF_REG_ARM_PC: u64 = 15;
pub const PERF_REG_ARM_MAX: u64 = 16;

pub const PERF_REG_MIPS_PC: u64 = 0;
pub const PERF_REG_MIPS_R1: u64 = 1;
pub const PERF_REG_MIPS_R2: u64 = 2;
pub const PERF_REG_MIPS_R3: u64 = 3;
pub const PERF_REG_MIPS_R4: u64 = 4;
pub const PERF_REG_MIPS_R5: u64 = 5;
pub const PERF_REG_MIPS_R6: u64 = 6;
pub const PERF_REG_MIPS_R7: u64 = 7;
pub const PERF_REG_MIPS_R8: u64 = 8;
pub const PERF_REG_MIPS_R9: u64 = 9;
pub const PERF_REG_MIPS_R10: u64 = 10;
pub const PERF_REG_MIPS_R11: u64 = 11;
pub const PERF_REG_MIPS_R12: u64 = 12;
pub const PERF_REG_MIPS_R13: u64 = 13;
pub const PERF_REG_MIPS_R14: u64 = 14;
pub const PERF_REG_MIPS_R15: u64 = 15;
pub const PERF_REG_MIPS_R16: u64 = 16;
pub const PERF_REG_MIPS_R17: u64 = 17;
pub const PERF_REG_MIPS_R18: u64 = 18;
pub const PERF_REG_MIPS_R19: u64 = 19;
pub const PERF_REG_MIPS_R20: u64 = 20;
pub const PERF_REG_MIPS_R21: u64 = 21;
pub const PERF_REG_MIPS_R22: u64 = 22;
pub const PERF_REG_MIPS_R23: u64 = 23;
pub const PERF_REG_MIPS_R24: u64 = 24;
pub const PERF_REG_MIPS_R25: u64 = 25;
pub const PERF_REG_MIPS_R28: u64 = 26;
pub const PERF_REG_MIPS_R29: u64 = 27;
pub const PERF_REG_MIPS_R30: u64 = 28;
pub const PERF_REG_MIPS_R31: u64 = 29;
pub const PERF_REG_MIPS_MAX: u64 = PERF_REG_MIPS_R31 + 1;

pub const PERF_REG_ARM64_X0: u64 = 0;
pub const PERF_REG_ARM64_X1: u64 = 1;
pub const PERF_REG_ARM64_X2: u64 = 2;
pub const PERF_REG_ARM64_X3: u64 = 3;
pub const PERF_REG_ARM64_X4: u64 = 4;
pub const PERF_REG_ARM64_X5: u64 = 5;
pub const PERF_REG_ARM64_X6: u64 = 6;
pub const PERF_REG_ARM64_X7: u64 = 7;
pub const PERF_REG_ARM64_X8: u64 = 8;
pub const PERF_REG_ARM64_X9: u64 = 9;
pub const PERF_REG_ARM64_X10: u64 = 10;
pub const PERF_REG_ARM64_X11: u64 = 11;
pub const PERF_REG_ARM64_X12: u64 = 12;
pub const PERF_REG_ARM64_X13: u64 = 13;
pub const PERF_REG_ARM64_X14: u64 = 14;
pub const PERF_REG_ARM64_X15: u64 = 15;
pub const PERF_REG_ARM64_X16: u64 = 16;
pub const PERF_REG_ARM64_X17: u64 = 17;
pub const PERF_REG_ARM64_X18: u64 = 18;
pub const PERF_REG_ARM64_X19: u64 = 19;
pub const PERF_REG_ARM64_X20: u64 = 20;
pub const PERF_REG_ARM64_X21: u64 = 21;
pub const PERF_REG_ARM64_X22: u64 = 22;
pub const PERF_REG_ARM64_X23: u64 = 23;
pub const PERF_REG_ARM64_X24: u64 = 24;
pub const PERF_REG_ARM64_X25: u64 = 25;
pub const PERF_REG_ARM64_X26: u64 = 26;
pub const PERF_REG_ARM64_X27: u64 = 27;
pub const PERF_REG_ARM64_X28: u64 = 28;
pub const PERF_REG_ARM64_X29: u64 = 29;
pub const PERF_REG_ARM64_LR: u64 = 30;
pub const PERF_REG_ARM64_SP: u64 = 31;
pub const PERF_REG_ARM64_PC: u64 = 32;
pub const PERF_REG_ARM64_MAX: u64 = 33;

pub const PERF_SAMPLE_REGS_ABI_32: u64 = 1;
pub const PERF_SAMPLE_REGS_ABI_64: u64 = 2;

mod ioctl {
    use libc::c_ulong;

    #[cfg( not( any( target_arch = "mips", target_arch = "mips64", target_arch = "powerpc", target_arch = "powerpc64" ) ) )]
    mod arch {
        use libc::c_ulong;

        pub const IOC_SIZEBITS: c_ulong = 14;
        pub const IOC_DIRBITS: c_ulong = 2;
        pub const IOC_NONE: c_ulong = 0;
    }

    #[cfg( any( target_arch = "mips", target_arch = "mips64", target_arch = "powerpc", target_arch = "powerpc64" ) )]
    mod arch {
        use libc::c_ulong;

        pub const IOC_SIZEBITS: c_ulong = 13;
        pub const IOC_DIRBITS: c_ulong = 3;
        pub const IOC_NONE: c_ulong = 1;
    }

    pub use self::arch::*;

    pub const IOC_NRSHIFT: c_ulong = 0;
    pub const IOC_NRBITS: c_ulong = 8;
    pub const IOC_TYPEBITS: c_ulong = 8;
    pub const IOC_TYPESHIFT: c_ulong = IOC_NRSHIFT + IOC_NRBITS;
    pub const IOC_SIZESHIFT: c_ulong = IOC_TYPESHIFT + IOC_TYPEBITS;
    pub const IOC_DIRSHIFT: c_ulong = IOC_SIZESHIFT + IOC_SIZEBITS;
}

macro_rules! ioc {
    ($dir:expr, $kind:expr, $nr:expr, $size:expr) => {
        ($dir  << ioctl::IOC_DIRSHIFT) |
        (($kind as c_ulong) << ioctl::IOC_TYPESHIFT) |
        ($nr   << ioctl::IOC_NRSHIFT) |
        ($size << ioctl::IOC_SIZESHIFT)
    }
}

macro_rules! io {
    ($kind:expr, $nr:expr) => {
        ioc!( ioctl::IOC_NONE, $kind, $nr, 0 )
    }
}

pub const PERF_EVENT_IOC_ENABLE: c_ulong = io!( b'$', 0 );
pub const PERF_EVENT_IOC_DISABLE: c_ulong = io!( b'$', 1 );

#[repr(C)]
pub struct PerfEventAttr {
    pub kind: u32,
    pub size: u32,
    pub config: u64,
    pub sample_period_or_freq: u64,
    pub sample_type: u64,
    pub read_format: u64,
    pub flags: u64,
    pub wakeup_events_or_watermark: u32,
    pub bp_type: u32,
    pub bp_addr_or_config: u64,
    pub bp_len_or_config: u64,
    pub branch_sample_type: u64,
    pub sample_regs_user: u64,
    pub sample_stack_user: u32,
    pub clock_id: i32,
    // Added in V4:
    // pub sample_regs_intr: u64,
    // Added in V5:
    // pub aux_watermark: u32,
    // pub reserved: u32
}

#[repr(C)]
pub struct PerfEventMmapPage {
    pub version: u32,
    pub compat_version: u32,
    pub lock: u32,
    pub index: u32,
    pub offset: i64,
    pub time_enabled: u64,
    pub time_running: u64,
    pub capabilities: u64,
    pub pmc_width: u16,
    pub time_shift: u16,
    pub time_mult: u32,
    pub time_offset: u64,
    pub time_zero: u64,
    pub size: u32,
    pub reserved: [u8; 118 * 8 + 4],
    pub data_head: u64,
    pub data_tail: u64,
    pub data_offset: u64,
    pub data_size: u64,
    pub aux_head: u64,
    pub aux_tail: u64,
    pub aux_offset: u64,
    pub aux_size: u64
}

impl fmt::Debug for PerfEventMmapPage {
    fn fmt( &self, fmt: &mut fmt::Formatter ) -> Result< (), fmt::Error > {
        fmt.debug_map()
            .entry( &"version", &self.version )
            .entry( &"compat_version", &self.compat_version )
            .entry( &"lock", &self.lock )
            .entry( &"index", &self.index )
            .entry( &"offset", &self.offset )
            .entry( &"time_enabled", &self.time_enabled )
            .entry( &"time_running", &self.time_running )
            .entry( &"capabilities", &self.capabilities )
            .entry( &"pmc_width", &self.pmc_width )
            .entry( &"time_shift", &self.time_shift )
            .entry( &"time_mult", &self.time_mult )
            .entry( &"time_offset", &self.time_offset )
            .entry( &"time_zero", &self.time_zero )
            .entry( &"size", &self.size )
            .entry( &"data_head", &self.data_head )
            .entry( &"data_tail", &self.data_tail )
            .entry( &"data_offset", &self.data_offset )
            .entry( &"data_size", &self.data_size )
            .entry( &"aux_head", &self.aux_head )
            .entry( &"aux_tail", &self.aux_tail )
            .entry( &"aux_offset", &self.aux_offset )
            .entry( &"aux_size", &self.aux_size )
            .finish()
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct PerfEventHeader {
    pub kind: u32,
    pub misc: u16,
    pub size: u16
}

#[cfg(target_arch = "x86")]
const PERF_EVENT_OPEN: usize = 336;

#[cfg(target_arch = "x86_64")]
const PERF_EVENT_OPEN: usize = 298;

#[cfg(target_arch = "mips")]
const PERF_EVENT_OPEN: usize = 4333;

#[cfg(target_arch = "powerpc")]
const PERF_EVENT_OPEN: usize = 319;

#[cfg(target_arch = "powerpc64")]
const PERF_EVENT_OPEN: usize = 319;

#[cfg(target_arch = "arm")]
const PERF_EVENT_OPEN: usize = 364;

#[cfg(target_arch = "sparc64")]
const PERF_EVENT_OPEN: usize = 327;

#[cfg(target_arch = "mips64")]
const PERF_EVENT_OPEN: usize = 5292;

#[cfg(target_arch = "aarch64")]
const PERF_EVENT_OPEN: usize = 241;

pub fn sys_perf_event_open( attr: &PerfEventAttr, pid: pid_t, cpu: c_int, group_fd: c_int, flags: c_ulong ) -> c_int {
    unsafe {
        syscall( PERF_EVENT_OPEN as _, attr as *const _, pid, cpu, group_fd, flags ) as c_int
    }
}
